#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>

#include "list.h"
#include "CommonUtill.h"
#include "Log.h"
#include "stringlib.h"
#include "../CharLength.h"

#include "MediaConnect.h"


#define MEDIA_SERVER_AUTH_PATH                       "/sos/v1/media/server/authenticate"

#define MEDIA_SERVER_MAX_RETRY_CONNECT               5

#define MEDIA_CHANNEL_INIT                          100
#define MEDIA_CHANNEL_START                         101
#define MEDIA_CHANNEL_CONNECTED                     102
#define MEDIA_CHANNEL_DISCONNECTED                  103

typedef struct{
    nw_ws *clientConnect;
    char taskCode[TASK_CODE_SIZE];
    char wsUrl[REQUEST_PATH_SIZE];
    int wsPort;
    int state;
    int reconnectCount;
    MediaDataCallback dataCallback;
}MSWsConnect;

typedef struct{
    char authCode[64];
    pthread_mutex_t mutex;
    bctbx_list_t *connectList;
    MediaAuthCodeCallback authCodeCallback;
    int isSyncAuthCode;
    int connectWithMS;
    int heartCount;
    int heartCheckCount;
}MeidaConnectManager;

static MeidaConnectManager connectManager;

static void mediaDataCallback(nw_ws *ws, int status, char *recvMsg, void *data, int msgLength);


static int isChannelConnected(MSWsConnect *connect){
    if(NULL == connect){
        return 0;
    }
    if(MEDIA_CHANNEL_CONNECTED == connect->state){
        return 1;
    }
    return 0;
}

static MSWsConnect *findConnectByTaskCode(const char *taskCode){
    if(is_empty_str(taskCode)){
        return NULL;
    }
    bctbx_list_t *elem = connectManager.connectList;
    MSWsConnect *temp = NULL;
    for (; NULL != elem; elem = elem->next){
        temp = (MSWsConnect *)elem->data;
        if(NULL == temp){
            continue;
        }
        if(0 == strcmp(taskCode, temp->taskCode)){
            return temp;
        }
    }
    return NULL;
}

static MSWsConnect *findConnectByWs(nw_ws *wsConnect){
    if(NULL == wsConnect){
        return NULL;
    }
    bctbx_list_t *elem = connectManager.connectList;
    MSWsConnect *temp = NULL;
    for (; NULL != elem; elem = elem->next){
        temp = (MSWsConnect *)elem->data;
        if(NULL == temp){
            continue;
        }
        if(wsConnect == temp->clientConnect){
            return temp;
        }
    }
    return NULL;
}

static void handleMediaChannelConnected(nw_ws *wsConnect){
    MSWsConnect *temp = findConnectByWs(wsConnect);
    if(NULL == temp){
        return;
    }
    if(strstr(temp->wsUrl, "video")){
        //刷新I帧
    }
    pthread_mutex_lock(&connectManager.mutex);
    temp->state = MEDIA_CHANNEL_CONNECTED;
    pthread_mutex_unlock(&connectManager.mutex);
}

static void handleMediaChannelMsgReceived(nw_ws *wsConnect, char *recvMsg, int msgLength){
    // log_d("handleMeidaChannelMsgReceived msgLength = %d", msgLength);
    if(NULL == recvMsg){
        return;
    }
    MSWsConnect *temp = findConnectByWs(wsConnect);
    if(NULL == temp){
        return;
    }
    if(NULL == temp->dataCallback){
        return;
    }
    // log_d("handleMeidaChannelMsgReceived callback for = %s", temp->taskCode);
    temp->dataCallback(temp->taskCode, recvMsg, msgLength);
}

static void rebuildMediaChannelCallback(void *timerInstance, void *userData){
    if(NULL == userData){
        log_w("rebuildMediaChannel cancel, the media task maybe cancel");
        return;
    }
    MSWsConnect *temp = (MSWsConnect *)userData;
    if(temp->reconnectCount > MEDIA_SERVER_MAX_RETRY_CONNECT){
        log_w("rebuildMediaChannel cancel, retry connect over %d times", MEDIA_SERVER_MAX_RETRY_CONNECT);
        media_release_data_channel(temp->taskCode);
        return;
    }
    
    char connectPath[512] = {0};
    snprintf(connectPath, 512, "%s?AuthCode=%s&TaskCode=%s", temp->wsUrl, connectManager.authCode, temp->taskCode);
    log_d("rebuildMediaChannel connectPath = %s", connectPath);
    nw_ws *tempConnect = sos_NwWsClientConnect(MEDIA_HTTP_SERVER_LOCAL_ADDRESS, 
    temp->wsPort, connectPath, NULL, NULL, 0, mediaDataCallback);
    if(NULL == tempConnect){
        log_e("rebuildMediaChannel failed !!");
        return;
    }

    pthread_mutex_lock(&connectManager.mutex);
    temp->reconnectCount++;
    temp->clientConnect = tempConnect;
    pthread_mutex_unlock(&connectManager.mutex);
}

static void handleMediaChannelDisConnected(nw_ws *wsConnect){
    MSWsConnect *temp = findConnectByWs(wsConnect);
    if(NULL == temp){
        return;
    }
    pthread_mutex_lock(&connectManager.mutex);
    temp->clientConnect = NULL;
    temp->state = MEDIA_CHANNEL_DISCONNECTED;
    pthread_mutex_unlock(&connectManager.mutex);
    //20s后重连媒体数据通道
    // nw_start_timer(20000, NW_FALSE, rebuildMediaChannelCallback, temp);
}

static void mediaDataCallback(nw_ws *ws, int status, char *recvMsg, void *data, int msgLength){  
    // log_d("mediaWsCallback msg = %s \n", recvMsg);
    switch (status){
        case NW_WS_CLIENT_STATUS_CONNECTED:
            handleMediaChannelConnected(ws);
            break;
        case NW_WS_CLIENT_STATUS_CONNECT_FAILED:
            log_i("mediaDataCallback CONNECT_FAILED, connect = %p", ws);
            handleMediaChannelDisConnected(ws);
            break;
        case NW_WS_CLIENT_STATUS_RECV_MSG:
            handleMediaChannelMsgReceived(ws, recvMsg, msgLength);
            break;
        case NW_WS_CLIENT_STATUS_DISCONNECTED:
            log_i("mediaDataCallback DISCONNECTED, connect = %p", ws);
            handleMediaChannelDisConnected(ws);
            break;
        default:
            log_i("mediaDataCallback not match any status type, connect = %p", ws);
            break;
    }
}

//+++++++++++++++++++++++++++++ media server notify 相关begin ++++++++++++++++++++++++++

static void handleNotifyMsgRecevied(nw_ws *ws, char *recvMsg, int msgLength){
    if(is_empty_str(recvMsg)){
        return;
    }
    if(NULL == ws){
        return;
    }
    if(0 == strcmp(MEDIA_NOTIFY_HEART_PING, recvMsg)){
        pthread_mutex_lock(&connectManager.mutex);
        connectManager.heartCount++;
        pthread_mutex_unlock(&connectManager.mutex);
        char heartRsp[16] = {0};
        snprintf(heartRsp, 16, "%s", MEDIA_NOTIFY_HEART_RESPONSE);
        log_d("sendOut Heart rspMsg = %s", heartRsp);
        sos_NwWsClientSendMsg(ws, heartRsp);
    }
}

static void mediaNotifyCallback(nw_ws *ws, int status, char *recvMsg, void *data, int msgLength){
    switch (status){
        case NW_WS_CLIENT_STATUS_CONNECTED:
            log_i("mediaNotifyCallback CONNECT_SUCCESS, connect = %p", ws);
            break;
        case NW_WS_CLIENT_STATUS_CONNECT_FAILED:
            log_i("mediaNotifyCallback CONNECT_FAILED, connect = %p", ws);
            
            break;
        case NW_WS_CLIENT_STATUS_RECV_MSG:
            handleNotifyMsgRecevied(ws, recvMsg, msgLength);
            break;
        case NW_WS_CLIENT_STATUS_DISCONNECTED:
            log_i("mediaNotifyCallback DISCONNECTED, connect = %p", ws);
            break;
        default:
            log_i("mediaNotifyCallback not match any status type, connect = %p", ws);
            break;
    }
}

static void buildMsgNotifyChannelWithMS(){
    char connectPath[128] = {0};
    snprintf(connectPath, 128, "%s/%s", MEDIA_API_WS_NOTIFY_PATH, MEDIA_SERVER_SUBCSCRIBE_ACCESCODE);
    log_d("buildMsgNotifyChannelWithMS connectPath = %s", connectPath);
    nw_ws *tempConnect = sos_NwWsClientConnect(MEDIA_HTTP_SERVER_LOCAL_ADDRESS, 
    MEDIA_WS_SERVER_LISTEN_PORT, connectPath, NULL, NULL, 0, mediaNotifyCallback);
    if(NULL == tempConnect){
        log_e("buildMsgNotifyChannelWithMS failed !!");
    }
}

//+++++++++++++++++++++++++++++ media server notify 相关end ++++++++++++++++++++++++++

//鉴权相关
static void mediaServerAuthCallback(sos_nw_rsp_t *rspBody, void *userData){
    int status = -1;
    cJSON *bodyJson = isRequestSuccess(rspBody, userData, &status, "mediaServerAuth");
    if (bodyJson == NULL || status != 0){
        sos_free(userData);
        pthread_mutex_lock(&connectManager.mutex);
        connectManager.isSyncAuthCode = 0;
        pthread_mutex_unlock(&connectManager.mutex);
        return;
    }
    cJSON *authCodeJson = cJSON_GetObjectItemCaseSensitive(bodyJson, "AuthCode");
    if (cJSON_IsString(authCodeJson) && !is_empty_str(authCodeJson->valuestring)) {
        pthread_mutex_lock(&connectManager.mutex);
        memset(connectManager.authCode, 0, sizeof(connectManager.authCode));
        snprintf(connectManager.authCode, sizeof(connectManager.authCode), "%s", authCodeJson->valuestring);
        pthread_mutex_unlock(&connectManager.mutex);
        if(NULL != connectManager.authCodeCallback){
            connectManager.authCodeCallback((char *)userData, connectManager.authCode);
        }
    }else{
        log_e("mediaServerAuth failed !!!");
    }
    sos_free(userData);
    cJSON_Delete(bodyJson);
    pthread_mutex_lock(&connectManager.mutex);
    connectManager.isSyncAuthCode = 0;
    pthread_mutex_unlock(&connectManager.mutex);
}

static void mediaServerAuthRequest(void *userData){
    if(connectManager.isSyncAuthCode){
        return;
    }
    pthread_mutex_lock(&connectManager.mutex);
    connectManager.isSyncAuthCode = 1;
    pthread_mutex_unlock(&connectManager.mutex);

    cJSON *obj = cJSON_CreateObject();
    cJSON_AddItemToObject(obj, "AccessCode", cJSON_CreateString(MEDIA_SERVER_SUBCSCRIBE_ACCESCODE));
    char *requestBody = cJSON_PrintUnformatted(obj);
    log_i("mediaServerAuthRequest request body msg content = %s", requestBody);

    int result = sos_NwHttpSendRequest(MEDIA_HTTP_SERVER_LOCAL_ADDRESS, 
    MEDIA_HTTP_SERVER_LISTEN_PORT, MEDIA_SERVER_AUTH_PATH, NW_HTTP_METHOD_POST, 
    requestBody, NW_DEFAULT_TOKEN, userData, 0, mediaServerAuthCallback);
    sos_free(requestBody);
    cJSON_Delete(obj);
}

int media_load_connect_moudle(){
    connectManager.connectList = NULL;
    connectManager.authCodeCallback = NULL;
    connectManager.isSyncAuthCode = 0;
    connectManager.connectWithMS = 0;
    connectManager.heartCount = 0;
    connectManager.heartCheckCount = 0;
    memset(connectManager.authCode, 0, sizeof(connectManager.authCode));
    pthread_mutex_init(&connectManager.mutex, NULL);
    buildMsgNotifyChannelWithMS();
    mediaServerAuthRequest(NULL);
    return 0;
}

const char *media_get_auth_code(){
    return connectManager.authCode;
}

int media_refresh_auth_code(void *userData, MediaAuthCodeCallback authCodeCallback){
    connectManager.authCodeCallback = authCodeCallback;
    mediaServerAuthRequest(userData);
    return 0;
}

int media_build_data_channel(const char *taskCode, const char *wsUrl, int wsPort, MediaDataCallback dataCallback){
    MSWsConnect *temp = findConnectByTaskCode(taskCode);
    if(isChannelConnected(temp)){
        return 0;
    }
    
    char connectPath[512] = {0};
    snprintf(connectPath, 512, "%s?AuthCode=%s&TaskCode=%s", wsUrl, connectManager.authCode, taskCode);
    log_d("media_build_data_channel connectPath = %s", connectPath);
    nw_ws *tempConnect = sos_NwWsClientConnect(MEDIA_HTTP_SERVER_LOCAL_ADDRESS, 
    wsPort, connectPath, NULL, NULL, 0, mediaDataCallback);
    if(NULL == tempConnect){
        log_e("media_build_data_channel failed !!");
        return -1;
    }

    MSWsConnect *connect = (MSWsConnect *)sos_malloc(sizeof(MSWsConnect), "MSWsConnect");
    if(NULL == connect){
        log_e("malloc MSWsConnect failed !!");
        return -1;
    }
    connect->dataCallback = dataCallback;
    connect->clientConnect = tempConnect;
    connect->wsPort = wsPort;
    connect->state = MEDIA_CHANNEL_INIT;
    connect->reconnectCount = 0;
    memset(connect->taskCode, 0 , sizeof(connect->taskCode));
    snprintf(connect->taskCode, sizeof(connect->taskCode), "%s", taskCode);
    memset(connect->wsUrl, 0 , sizeof(connect->wsUrl));
    snprintf(connect->wsUrl, sizeof(connect->wsUrl), "%s", wsUrl);

    pthread_mutex_lock(&connectManager.mutex);
    connectManager.connectList = bctbx_list_append(connectManager.connectList, connect);
    pthread_mutex_unlock(&connectManager.mutex);
    log_d("media_build_data_channel success = %p, for %s", connect, taskCode);
    return 0;
}

int media_release_data_channel(const char *taskCode){
    MSWsConnect *temp = findConnectByTaskCode(taskCode);
    if(NULL == temp){
        log_e("not exsit data channel for %s !!", taskCode);
        return -1;
    }
    sos_NwWsClientDisconnect(temp->clientConnect);
    pthread_mutex_lock(&connectManager.mutex);
    connectManager.connectList = bctbx_list_remove(connectManager.connectList, temp);
    sos_free(temp);
    pthread_mutex_unlock(&connectManager.mutex);
    log_d("media_release_data_channel finish for %s", taskCode);
    return 0;
}

int media_check_alive(){
    connectManager.heartCheckCount++;
    int diff = connectManager.heartCheckCount - connectManager.heartCount;
    log_d("media_check_alive heartCheckCount = %d, heartCount = %d", 
    connectManager.heartCheckCount, connectManager.heartCount);
    if(diff <= 0){
        pthread_mutex_lock(&connectManager.mutex);
        connectManager.heartCount = 0;
        connectManager.heartCheckCount = 0;
        pthread_mutex_unlock(&connectManager.mutex);
        return 0;
    }
    if(diff > 3){
        log_d("media_check_alive diff = %d, MediaServr maybe disconnect, try restart mediaChannel", diff);
    }
    return 0;
}

cJSON *isRequestSuccess(sos_nw_rsp_t *rspBody, void *userData, int *status, const char *tag){
    if (rspBody == NULL){
        log_e("%s callback rspBody is null", tag);
        return NULL;
    }
    log_d("%s callback path = %s", tag, rspBody->path);
    log_d("%s callback msg = %s ", tag, rspBody->msgBody);
    log_d("%s callback status = %d ", tag, rspBody->status);
    if (rspBody->status != 200){
        log_e("%s callback http error code = %d", tag, rspBody->status);
        return NULL;
    }
    cJSON *bodyJson = cJSON_Parse(rspBody->msgBody);
    if (bodyJson == NULL) {
        const char *error_ptr = cJSON_GetErrorPtr();
		log_e("%s callback parse content failed!", tag);
		return NULL;
    }
    cJSON *statusJson = cJSON_GetObjectItemCaseSensitive(bodyJson, "Status");
    if (cJSON_IsNumber(statusJson) && (statusJson->valueint != 0)) {
        *status = statusJson->valueint;
        
        cJSON *statusMsgJson = cJSON_GetObjectItemCaseSensitive(bodyJson, "StatusMsg");
        char errorMsg[RSP_STATUS_MSG_SIZE];
        if (cJSON_IsString(statusMsgJson) && (statusMsgJson->valuestring != NULL)) {
            memset(errorMsg, 0, sizeof(errorMsg));
            snprintf(errorMsg, sizeof(errorMsg), "%s", statusMsgJson->valuestring);
        }
        log_w("excute %s request return failed, status = %d, StatusMsg = %s", tag, statusJson->valueint, errorMsg);
        cJSON_Delete(bodyJson);
        return NULL;
    }
    *status = 0;
    return bodyJson;
}